#include "iConfig.hpp"

#include <glf/debugger/profiler.h>
#include <glf/core/timer.h>
#include <glf/debug/console.h>

namespace
{
	GLF_DECLARE_TLS(unsigned int, EventStackDepth, extern);
	GLF_DEFINE_TLS(unsigned int, EventStackDepth) = { 0 };
}

namespace glf
{
namespace debugger
{
	
#if _WIN32
	bool EventType::sDefaultMode = true;
#else
	bool EventType::sDefaultMode = false;
#endif
	
	EventType sDefaultEventType("<default>");
	EventType sConditionEventType("glf/Synchronization/Condition", false);
	EventType sMutexEventType("glf/Synchronization/Mutex", false);
	EventType sSemaphoreEventType("glf/Synchronization/Semaphore", false);
	EventType sThreadEventType("glf/Thread", false);
	EventType sStreamEventType("glf/Stream", false);

#if GLF_ENABLE_DEBUGGER
	static const int STRING_BLOCK_CAPACITY = 65536;

	const char* StringAllocator::Allocate(const char* str)
	{
		const int size = strlen(str) + 1;

		if(mBlocks.size() == 0 || size > mCurrentSizeAvailable)
		{
			mBlocks.push_back((char*)glf::debugger::Alloc(STRING_BLOCK_CAPACITY));
			mCurrentSizeAvailable = STRING_BLOCK_CAPACITY;
		}

		char* ptr = mBlocks.back() + (STRING_BLOCK_CAPACITY - mCurrentSizeAvailable);
		mCurrentSizeAvailable -= size;
		memcpy(ptr, str, size);
		return ptr;
	}

	void StringAllocator::Free()
	{
		for(uint i = 0; i < mBlocks.size(); i++)
		{
			glf::debugger::Free(mBlocks[i]);
		}
		mBlocks.resize(0);
		mCurrentSizeAvailable = 0;
	}

	Profiler::StoredEvent* Profiler::EventCollector::AllocateEvent()
	{
		if(mEventPools.size() == 0 || mEventPools.back()->mSize >= EVENT_BLOCK_CAPACITY)
		{
			mEventPools.push_back((EventPool*)glf::debugger::Alloc(sizeof(EventPool)));
			mEventPools.back()->mSize = 0;
		}

		EventPool& pool = *mEventPools.back();
		StoredEvent* event = pool.mEvents + pool.mSize;
		pool.mSize++;
		return event;
	}

	uint Profiler::EventCollector::GetEventCount()
	{
		uint count = 0;
		for(uint i = 0; i < mEventPools.size(); i++)
		{
			count += mEventPools[i]->mSize;
		}
		return count;
	}

	void Profiler::EventCollector::FreeEvents()
	{
		for(uint i = 0; i < mEventPools.size(); i++)
		{
			glf::debugger::Free(mEventPools[i]);
		}
		mEventPools.resize(0);
	}

	EventType::EventType(const char* context, bool enabled) :
		mContext(context),
		mEnabled(enabled),
		mID(0)
	{
		Profiler::GetInstance()->RegisterEventType(*this);
	}

	GLF_DEFINE_SINGLETON_PLACEMENT_NEW(Profiler)

	Profiler::Profiler() :
		Module("PROFILER"),
		mMainThreadID(0),
		mEnabled(false),
		mCrtDbgFrequencyType(CRTDBG_NEVER),
		mBeginEventCallback(NULL),
		mEndEventCallback(NULL),
		mEventTypesChanged(true)
	{
	}

	Profiler::~Profiler()
	{
		ClearProfilerCollectors();
		glf::debugger::Debugger::GetInstance()->UnregisterModule( this );
	}

	void Profiler::UpdateFrame()
	{
		mMainThreadID = glf::Thread::GetSequentialThreadId();

		if(mEnabled)
		{
			// add new collector to profiler for current frame
			mProfilerEventCollectors.push_back(new EventCollector());
		}
		else if(!mEnabled && mProfilerEventCollectors.size() > 0)
		{
			if(GetDebugger()->IsConnected())
				SendEvents();

			ClearProfilerCollectors();
		}

		if(GetDebugger()->IsConnected())
		{
			SendEventTypes();
		}

#ifdef WIN32
		if(mCrtDbgFrequencyType == CRTDBG_FRAME)
		{
			GLF_ASSERT(_CrtCheckMemory());
		}
#endif
	}

	void Profiler::BeginEvent(const Event& event)
	{
		EventStackDepth.get()++;

		if(mBeginEventCallback != NULL)
			mBeginEventCallback(event.mName);

		if(GetDebugger() == NULL || !GetDebugger()->IsConnected())
			return;

#ifdef WIN32
		if(mCrtDbgFrequencyType == CRTDBG_SCOPE_EVENTS)
		{
			GLF_ASSERT(_CrtCheckMemory());
		}
#endif

		if(!mEnabled)
			return;

		const int threadID = glf::Thread::GetSequentialThreadId();
		GLF_ASSERT(threadID < GLF_DEBUGGER_MAXIMUM_THREAD_COUNT);
		ThreadInfos& eventInfos = mThreadInfos[threadID];
		EventCollector& collector = mMainThreadID == threadID ? *mProfilerEventCollectors.back() : eventInfos.mCollector;
		StringAllocator& stringAllocator = eventInfos.mStringAllocator;

		if(!eventInfos.mIsNameSet) {
			eventInfos.mIsNameSet = true;
			eventInfos.mName = Thread::GetCurrent().GetName().c_str();
		}

		if(collector.mEventStackIndex < 0)
			collector.mEventStackIndex = 0;

		while(EventStackDepth.get() > (uint)(collector.mEventStackIndex + 1))
		{
			StoredEvent& sevent = *collector.AllocateEvent();
			sevent.mBegin = GetMicroseconds();
			sevent.mEnd = sevent.mBegin;
			sevent.mDepth = collector.mEventStackIndex;
			sevent.mTimeEvent = false;
			
			sevent.mName = stringAllocator.Allocate("Event begin not catched");
			sevent.mDesc = NULL;

			collector.mEventStack[collector.mEventStackIndex] = &sevent;
			collector.mEventStackIndex++;
		}

		StoredEvent& sevent = *collector.AllocateEvent();
		sevent.mBegin = GetMicroseconds();
		sevent.mEnd = sevent.mBegin;
		sevent.mDepth = collector.mEventStackIndex;
		sevent.mTimeEvent = event.mTimeEvent;
		
		sevent.mName = event.mDuplicateName ? stringAllocator.Allocate(event.mName) : event.mName;
		sevent.mDesc = event.mDesc != NULL ? stringAllocator.Allocate(event.mDesc) : NULL;

		collector.mEventStack[collector.mEventStackIndex] = &sevent;
		collector.mEventStackIndex++;
	}

	void Profiler::EndEvent()
	{
		EventStackDepth.get()--;

		if(mEndEventCallback != NULL)
			mEndEventCallback();

		if(GetDebugger() == NULL || !GetDebugger()->IsConnected())
			return;

#ifdef WIN32
		if(mCrtDbgFrequencyType == CRTDBG_SCOPE_EVENTS)
		{
			GLF_ASSERT(_CrtCheckMemory());
		}
#endif

		if(!mEnabled)
			return;

		const int threadID = glf::Thread::GetSequentialThreadId();
		GLF_ASSERT(threadID < GLF_DEBUGGER_MAXIMUM_THREAD_COUNT);
		EventCollector& collector = mMainThreadID == threadID ? *mProfilerEventCollectors.back() : mThreadInfos[threadID].mCollector;

		collector.mEventStackIndex--;

		if(collector.mEventStackIndex >= 0 && !collector.mEventPools.empty())
		{
			StoredEvent& event = *collector.mEventStack[collector.mEventStackIndex];
			event.mEnd = GetMicroseconds();
		}
	}

	void Profiler::AddTimeEvent(const Event& event, const EventType& eventType)
	{
		if(eventType.IsEnabled())
		{
			Event timeEvent = event;
			timeEvent.mTimeEvent = true;

			BeginEvent(timeEvent);
			EndEvent();
		}
	}

	void Profiler::RegisterEventCallbacks(BeginEventCallback begin, EndEventCallback end)
	{
		mBeginEventCallback = begin;
		mEndEventCallback = end;
	}

	void Profiler::RegisterEventType(EventType& eventType)
	{
		ScopeMutex mutex;

		EventTypesMap::const_iterator it = mEventTypesMap.find(eventType.mContext);
		if(it != mEventTypesMap.end())
		{
			// slave type
			eventType.mID = it->second->mID;
			eventType.mEnabled = it->second->mEnabled;
			mSlaveEventTypes.push_back(&eventType);
		}
		else
		{
			// new type
			eventType.mID = mEventTypes.size();
			mEventTypes.push_back(&eventType);
			std::pair<const stringdbg, EventType*> pair(eventType.mContext, &eventType);
			mEventTypesMap.insert(pair);
			mEventTypesChanged = true;
		}
	}

	void Profiler::ClearProfilerCollectors()
	{
		for(uint i = 0; i < mProfilerEventCollectors.size(); i++)
			delete mProfilerEventCollectors[i];
		mProfilerEventCollectors.clear();
		
		// reset threads
		for(int i = 0; i < GLF_DEBUGGER_MAXIMUM_THREAD_COUNT; i++)
		{
			ThreadInfos& info = mThreadInfos[i];

			info.mIsNameSet = false;
			info.mCollector.mEventPools.resize(0);
			info.mCollector.mEventStackIndex = 0;
			info.mStringAllocator.Free();
		}
	}

	void Profiler::Parse(int type, PacketReader& in)
	{
		switch(type)
		{
			case MESSAGE_START:
			{
				Start();
				break;
			}

			case MESSAGE_STOP:
			{
				mEnabled = false;
				break;
			}

			case MESSAGE_SET_EVENT_TYPES:
			{
				ScopeMutex mutex;

				int count = in.ReadIntLE();
				for(int i = 0; i < count; i++)
				{
					int id = in.ReadIntLE();
					bool enabled = in.ReadByte() != 0;

					if(id >= 0 && id < (int)mEventTypes.size())
					{
						mEventTypes[id]->mEnabled = enabled;
					}
				}

				// update slaves
				for(size_t i = 0; i < mSlaveEventTypes.size(); i++)
				{
					EventType* slave = mSlaveEventTypes[i];
					slave->mEnabled = mEventTypes[slave->mID]->mEnabled;
				}
				break;
			}
		}
	}

	void Profiler::SetEnableAllEventType(bool value)
	{
		for(size_t i = 0; i < mEventTypes.size(); i++)
		{
			mEventTypes[i]->mEnabled = value;
		}

		for(size_t i = 0; i < mSlaveEventTypes.size(); i++)
		{
			mSlaveEventTypes[i]->mEnabled = value;
		}
	}

	void Profiler::ConnectionOpened()
	{
		mEventTypesChanged = true;
	}

	void Profiler::ConnectionClosed()
	{
		mEnabled = false;
	}

	void Profiler::SendEvents()
	{
		// sleep to ensure other threads exit BeginEvent/EndEvent
		glf::Thread::Sleep(10);

		ScopeMutex mutex;

		if(mProfilerEventCollectors.size() <= 1)
			return;

		// ignore last frame as it may be incomplete
		uint frameCount = mProfilerEventCollectors.size() - 1;
		uint64 ref = mProfilerEventCollectors.size() > 0 && mProfilerEventCollectors.front()->mEventPools.size() > 0 ? mProfilerEventCollectors.front()->mEventPools.front()->mEvents[0].mBegin : 0;

		// collect used threads
		ThreadInfos* usedThreadInfos[GLF_DEBUGGER_MAXIMUM_THREAD_COUNT] = {0};
		int usedThreadCount = 0;
		for(int i = 0; i < GLF_DEBUGGER_MAXIMUM_THREAD_COUNT; i++)
		{
			if(mThreadInfos[i].mCollector.mEventPools.size() > 0)
			{
				usedThreadInfos[usedThreadCount++] = &mThreadInfos[i];
			}
		}

		PacketWriter& pw = GetPacketWriter();
		pw.Init(MESSAGE_BEGIN_EVENTS);
		pw.Write((uint)0); //(MainThread->GetPriority());
		pw.Write(frameCount);
		pw.Write(usedThreadCount);
		
		// property map
		const PropertyMap::map_t& propertyMap = PropertyMap::Instance().GetProperties();
		pw.Write((uint)propertyMap.size());
		for(PropertyMap::map_t::const_iterator it = propertyMap.begin(); it != propertyMap.end(); ++it)
		{
			pw.Write(it->first.c_str());
			pw.Write(it->second.getAsString().c_str());
		}

		Send(pw);

		// send frames
		for(uint c = 0; c < frameCount; c++)
		{
			EventCollector& collector = *mProfilerEventCollectors[c];
			
			pw.Init(MESSAGE_FRAME_EVENTS);
			pw.Write(collector.GetEventCount());

			for(uint i = 0; i < collector.mEventPools.size(); i++)
			{
				const EventPool& pool = *collector.mEventPools[i];
				for(uint e = 0; e < pool.mSize; e++)
				{
					const StoredEvent& event = pool.mEvents[e];
					
					pw.Write(event.mName);
					pw.Write(event.mBegin - ref);
					pw.Write(event.mEnd - ref);
					pw.Write(event.mDepth);
					pw.Write(event.mTimeEvent);
					pw.Write(event.mDesc != NULL ? event.mDesc : "");
				}
			}

			Send(pw);
		}

		// send used threads
		for(int i = 0; i < usedThreadCount; i++)
		{
			ThreadInfos* info = usedThreadInfos[i];
			pw.Init(MESSAGE_THREAD_EVENTS);
			pw.Write(info->mPriority);
			pw.Write(info->mName.c_str());
			EventCollector& collector = info->mCollector;
			pw.Write(collector.GetEventCount());

			for(uint i = 0; i < collector.mEventPools.size(); i++)
			{
				const EventPool& pool = *collector.mEventPools[i];
				for(uint e = 0; e < pool.mSize; e++)
				{
					const StoredEvent& event = pool.mEvents[e];
				
					pw.Write(event.mName);
					pw.Write(event.mBegin - ref);
					pw.Write(event.mEnd - ref);
					pw.Write(event.mDepth);
					pw.Write(event.mTimeEvent);
					pw.Write(event.mDesc != NULL ? event.mDesc : "");
				}
			}

			Send(pw);
		}

		pw.Init(MESSAGE_END_EVENTS);
		Send(pw);
	}

	void Profiler::SendEventTypes()
	{
		ScopeMutex mutex;

		if(mEventTypesChanged)
		{
			PacketWriter& pw = GetPacketWriter();
			pw.Init(MESSAGE_EVENT_TYPES);
			pw.Write((uint)mEventTypes.size());
			
			for(size_t i = 0; i < mEventTypes.size(); i++)
			{
				EventType* type = mEventTypes[i];
				pw.Write(type->mContext.c_str());
				pw.Write(type->mEnabled);
				pw.Write(type->mID);
			}
			
			Send(pw);

			mEventTypesChanged = false;
		}
	}

	void Profiler::Start()
	{
		if(!mEnabled)
		{
			ClearProfilerCollectors();

			// add new collector to profiler for current frame
			mProfilerEventCollectors.reserve(128);
			mProfilerEventCollectors.push_back(new EventCollector());

			mEnabled = true;
		}
	}

	void Profiler::Stop()
	{
		if(mEnabled)
		{
			mEnabled = false;

			if(GetDebugger()->IsConnected())
				SendEvents();

			ClearProfilerCollectors();
		}
	}

#endif // GLF_ENABLE_DEBUGGER

//careful, this impl needs to be empty so it's obfuscated unless #define explicitly want profiling information.
VScopeEvent::VScopeEvent(const char* name, ...)
{
	//dont do the string manipulation if no actual profiling scope are used.
#if GLF_ENABLE_DEBUGGER || (GLF_PSP2_ENABLE_PERF && GLF_PLATFORM_PSP2)
	const char* scopeName = "";

#if GLF_PSP2_ENABLE_PERF && GLF_PLATFORM_PSP2
	if(true)
#else
	if(debugger::Debugger::GetInstance()->IsConnected())
#endif
	{
		va_list vlist;
		va_start(vlist, name);
		ConsoleScopeBuffer buf;

		scopeName = buf.VPrint(name, vlist);
		va_end(vlist);
	}
#endif

#if GLF_ENABLE_DEBUGGER
	Profiler::Event event(scopeName);
	Profiler::GetInstance()->BeginEvent(event);
#endif

#if GLF_PLATFORM_PSP2 && GLF_PSP2_ENABLE_PERF
#   if SCE_PSP2_SDK_VERSION < 0x00996070
    sceRazorCpuPushMarker(scopeName);
#   else
    // Ok, now that's a strange API change, if we want to set a normal
    // marker, we have to set a marker with HUD, but with HUD disabled O_o
	sceRazorCpuPushMarkerWithHud(scopeName, 0, SCE_RAZOR_MARKER_DISABLE_HUD);
#   endif
#endif


}

} //namespace debugger
} //namespace glf


